import java.util.*;

/** 
 * A Room contains a name, it's capacity, open time, close time and items
 * within the room. It also contains bookings for the room.
 */
public class Room implements Comparable<Object>
{
    private String name;
    private int capacity;
    private Time openTime;
    private Time closeTime;
    private Map<String, Integer> items;
    
    private boolean overnight;

    // A map and a list are used to keep ordering as well as easy access
    // to bookings through their name
    private List<Booking> bookingsList;
    private Map<String, Booking> bookingsMap;

    /** 
     * Constructor creates space for bookings and items. Room created as a room
     * that is open during the day. This is changed only when open and close
     * times are appropriate for holding the overnight status.
     */
    public Room()
    {
        items = new HashMap<String, Integer>();
        bookingsList = new ArrayList<Booking>();
        bookingsMap = new HashMap<String, Booking>();
        overnight = false;
    }

    // Strictly used for testing - remove in final release
    public Map<String, Booking> getBookingsMap()
    {
        return bookingsMap;
    }

    // Getters

    /** 
     * Returns the room name.
     */
    public String getName()
    {
        return name;
    }

    /** 
     * Returns the room's capacity.
     */
    public int getCapacity()
    {
        return capacity;
    }

    /** 
     * Returns the room's open time.
     */
    public Time getOpenTime()
    {
        return openTime;
    }

    /** 
     * Returns the room's close time.
     */
    public Time getCloseTime()
    {
        return closeTime;
    }

    /** 
     * Returns the room's bookings in order as defined in compareTo(). Only a
     * copy of the list is returned.
     */
    public Booking[] getOrderedBookings()
    {
        // Hiding original list by creating copy of it
        Booking[] copyBookings = new Booking[bookingsList.size()];
        bookingsList.toArray(copyBookings);

        return copyBookings;
    }

    /** 
     * Returns the room's items. Only a copy of the list is returned.
     */
    public Map<String, Integer> getItems()
    {
        // Hiding original list by creating copy of it
        Map<String, Integer> copyItems = new HashMap<String, Integer>();
        copyItems.putAll(items);

        return copyItems;
    }

    // Setters

    /**
     * Sets the room name.
     *
     * @param roomName room name
     */
    public void setName(String roomName)
    {
        name = roomName;
    }

    /**
     * Sets the room's capacity.
     *
     * @param roomCap room capacity
     */
    public void setCapacity(int roomCap)
    {
        capacity = roomCap;
    }

    /**
     * Sets the open time.
     *
     * @param oT open time
     */
    public void setOpenTime(Time oT)
    {
        openTime = oT;
    }

    /**
     * Sets the close time. When the close time is after the open time, it
     * checks whether the room is meant to be open overnight. If yes then the
     * room's close time is set and the room is marked as an overnight room. If
     * no then a correct time can be re-entered. If being read from a file then
     * the function assigns overnight to the room where appropriate.
     *
     * @param cT close time
     * @param fromFile to check whether close time is being set from a file
     */
    public void setCloseTime(Time cT, boolean fromFile)
    {        
        if(cT.before(openTime) && fromFile == false)
        {
            System.out.println("Are you sure this room is open overnight?");
            String answer = Read.systemIn();

            if(answer.equals("y"))
            {
                closeTime = cT;
                overnight = true;
            }
            else
            {
                System.out.print("Please enter the correct time: ");
                setCloseTime(Read.time(""),fromFile);
            }
        }
        else if(cT.before(openTime) && fromFile)
        {
            closeTime = cT;
            overnight = true;
        }
        else
        {
            closeTime = cT;
        }
    }

    /**
     * Sets the items.
     *
     * @param i items
     */
    public void setItems(Map<String, Integer> i)
    {
        items = i;
    }

    // Functions

    /** 
     * Checks for a booking clash over all bookings in the room. A clash is
     * present only if bookings overlap. This means that for a particular day,
     * if the start time of one booking matches the end time of another, the
     * isClash() function will return false.
     * 
     * @param startTime start date/time of proposed booking
     * @param endTime end date/time of proposed booking
     */
    public boolean isClash(Date startTime, Date endTime)
    {
        boolean check = false;
        
        for(Booking booking: bookingsList)
        {
            Date currentStartTime = booking.getStartTime();
            Date currentEndTime = booking.getEndTime();

            // Tests all possibilities of clashing
            if (booking.isClash(startTime, endTime))
            {
                check = true;
                break;
            }

            if(booking instanceof RecurringBooking)
            {
                if(((RecurringBooking) booking).week(startTime, endTime))
                {
                    check = true;
                    break;
                }
            }
        }

        return check;
    }

    /** 
     * Checks whether the room is open so the proposed booking can be created.
     * The booking has already ben checked for validity, so this function
     * checks whether it's an overnight booking, an overnight room and whether
     * the timings of the booking work with the timings of gthe room.
     * 
     * @param startDate start date/time of proposed booking
     * @param endDate end date/time of proposed booking
     * 
     * @return true if room has opening times that work with the proposed
     * booking, otherwise false
     */
    public boolean isOpen(Date startDate, Date endDate)
    {
        boolean check = false;

        // Calendars created from dates so that the hour and minute of both
        // the start and end dates of the booking are accessbile
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDate);
        
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(endDate);

        // Hours and minutes from both the start and end dates of the booking
        int startHour = startCal.get(Calendar.HOUR_OF_DAY);
        int startMinute = startCal.get(Calendar.MINUTE);
        
        int endHour = endCal.get(Calendar.HOUR_OF_DAY);
        int endMinute = endCal.get(Calendar.MINUTE);

        // startDate and endDate of the booking have been converted to startTime
        // and endTime of the booking so the before and after functions of the
        // included Time class can be utilised.
        Time startTime = new Time(startHour, startMinute);
        Time endTime = new Time(endHour, endMinute);

        // Boolean used to check whether the room is open during the day or
        // overnight
        boolean dayRoom = (startTime.before(endTime)) ? true : false ;

        // If statement that checks the combination of open and close times
        // with whether the booking is overnight and whether the room is open
        // during the day or overnight
        if ((startTime.after(openTime)) && (endTime.before(closeTime)))
        {
            if ((dayRoom && (overnight == false)))
            {
                check = true;
            }
            if(overnight)
            {
                check = true;
            }
        }
        else if (overnight)
        {
            if ((startTime.after(openTime) && endTime.before(new Time(23,59)))||
            (startTime.after(new Time(0,0)) && endTime.before(closeTime)))
            {
                check = true;
            }
        }

        return check;
    }

    /* To be implemented
    public boolean removeBooking(String bookingName)
    {
        return true; // If removed
    }*/

    /** 
     * Inserts items into HashMap<String, Integer>. Currently only one item of
     * each type is entered
     *
     * @param i array of items (item names)
     */
    public void addItems(String[] i)
    {
        for (String item: i)
        {
            items.put(item, 1);
        }
    }

    /*
    // Used only in Test.java
    public void addItem(String i, Integer n)
    {
        items.put(i,n);
    }
    */

    /** 
     * Adds a booking to the room in natural order as defined in compareTo()
     *
     * @param booking booking to be added to room
     * @param bookingName booking name to be set in function
     */
    public void addBooking(Booking booking, String bookingName)
    {
        int i = 0;
        
        booking.setBookingName(bookingName);

        // Add booking in correct position on natural ordering explained in
        // function compareTo()
        for(i = 0; i < bookingsList.size(); i++)
        {
            bookingsMap.put(bookingName, booking);

            if(bookingsList.get(i).compareTo(booking) == 1)
            {
                bookingsList.add(i, booking);
                break;
            }
        }

        // If there are no items in the list or the item needs to be added at
        // the end for ordering to be correct
        if(i == bookingsList.size())
        {
            bookingsList.add(booking);
            bookingsMap.put(bookingName, booking);
        }
    }

    /** 
     * Used to check whether the room contains the items needed by the user.
     *
     * @param itemsWanted items required by caller
     */
    public boolean containsItems(Map<String, Integer> itemsWanted)
    {
        boolean check = true;

        for(String item : itemsWanted.keySet())
        {
            if(item.equals(""))
            {
                break;
            }

            if(items.containsKey(item))
            {
                check = true;
                continue;
            }
            else
            {
                check = false;
                break;
            }
        }

        return check;
    }

    /** 
     * Used to compare rooms on capacity.
     * 
     * @return -1,0,1 when Room o has a smaller, equal to or larger capacity
     * than this Room
     */
    public int compareTo(Object o) throws ClassCastException
    {
        int value = 0;

        if(o instanceof Room)
        {
            Room room = (Room) o;

            if(this.getCapacity() > room.getCapacity())
            {
                value = 1;
            }
            else if(this.getCapacity() < room.getCapacity())
            {
                value = -1;
            }
        }

        return value;
    }
}

